
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Shiro - 学习卡片</title>
      <style>
        body { font-family: sans-serif; background-color: #f0f8ff; color: #333; display: flex; flex-direction: column; align-items: center; padding: 50px 20px; }
        .header h1 { font-size: 32px; }
        .grid-container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 28px; width: 100%; max-width: 1200px; }
        .card-container { perspective: 1200px; cursor: pointer; height: 250px; }
        .card { width: 100%; height: 100%; position: relative; transform-style: preserve-3d; transition: transform 0.7s; border-radius: 16px; box-shadow: 0 4px 16px rgba(0,0,0,0.08); }
        .card-container.flipped .card { transform: rotateY(180deg); }
        .card-face { position: absolute; width: 100%; height: 100%; backface-visibility: hidden; display: flex; flex-direction: column; box-sizing: border-box; border-radius: 16px; background-color: #fff; padding: 24px; }
        .card-back { background-color: #f0fff4; transform: rotateY(180deg); justify-content: space-between; }
        .card-category { font-size: 14px; color: #0052d9; margin-bottom: 8px; font-weight: 500; }
        .card-question { font-size: 20px; font-weight: 500; flex-grow: 1; display: flex; align-items: center; justify-content: center; text-align: center; }
        .card-answer-wrapper { flex-grow: 1; overflow-y: auto; }
        .card-answer { font-size: 15px; line-height: 1.7; }
        .card-footer { font-size: 13px; color: #8a919f; border-top: 1px solid #f0f0f0; padding-top: 16px; margin-top: 16px; }
        .card-source { font-size: 13px; color: #8a919f; border-top: 1px solid #f0f0f0; padding-top: 12px; margin-top: 12px; }
      </style>
    </head>
    <body>
      <div class="header">
        <h1>Shiro - 学习卡片</h1>
      </div>
      <div class="grid-container">
        
    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">理论</div>
          <div class="card-question">Apache Shiro 架构的三个核心组件分别是什么，它们各自扮演什么角色？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">理论</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">Shiro 的三个核心组件是：<br>1. Subject：代表当前与系统交互的用户或服务。所有 Subject 实例都必须绑定到一个 SecurityManager。<br>2. SecurityManager：Shiro 架构的核心，用于协调内部安全组件，管理组件实例并提供安全服务。所有 Subject 的安全操作实际上都由幕后的 SecurityManager 处理。<br>3. Realms：本质上是特定安全的 DAO，用于进行身份验证和/或授权。Shiro 从 Realm 获取安全数据（如用户、角色、权限）。</div>
          </div>
          <div class="card-source">来源: 1.2 功能简介-shiro的三个核心组件</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">机制</div>
          <div class="card-question">在自定义 Realm 以实现身份认证和授权时，通常需要继承 AuthorizingRealm 类并复写哪两个核心方法？这两个方法的作用分别是什么？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">机制</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">需要复写两个核心方法：<br>1. doGetAuthenticationInfo(AuthenticationToken token)：用于获取身份验证信息。当用户登录时，Shiro 会调用此方法，通过 token 获取登录信息并进行验证。<br>2. doGetAuthorizationInfo(PrincipalCollection principals)：用于获取授权信息。当需要进行权限校验时，Shiro 会调用此方法来获取当前用户的角色和权限。</div>
          </div>
          <div class="card-source">来源: 1.5.1 Realm</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">技术</div>
          <div class="card-question">在 Shiro 中如何为用户密码配置 MD5 加密？需要配置哪个类以及其关键属性？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">技术</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">通过替换 Realm 的 credentialsMatcher 属性来实现密码加密。具体配置如下：<br>1. 使用 org.apache.shiro.authc.credential.HashedCredentialsMatcher 类。<br>2. 配置其关键属性：'hashAlgorithmName' 设置为加密算法（如 "MD5"），'hashIterations' 设置加密次数（如 1024）。</div>
          </div>
          <div class="card-source">来源: 1.6 密码加密-修改Realm的配置</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">特性</div>
          <div class="card-question">在 Shiro 的 URL 过滤器链中，`authc` 和 `user` 过滤器有什么关键区别？它们分别适用于什么场景？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">特性</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">`authc` 和 `user` 的关键区别在于：<br>- `authc`：要求用户必须经过身份验证（即调用 Subject.login 成功登录）后才能访问。适用于支付等关键操作，即使是“记住我”的用户也需要重新登录。<br>- `user`：要求用户已经过身份验证或者通过“记住我”（RememberMe）功能登录的都可以访问。适用于大多数需要用户身份的普通页面。</div>
          </div>
          <div class="card-source">来源: 1.8 RememberMe</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">机制</div>
          <div class="card-question">为什么需要为 Shiro 的 Realm 配置缓存？默认情况下，认证信息和授权信息哪一个会开启缓存，为什么？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">机制</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">为 Realm 配置缓存是为了避免性能问题。如果没有缓存，每次请求需要授权时，Shiro 都会调用 Realm 从数据库查询权限，开销很大。通过缓存，权限信息只需查询一次。默认情况下，Shiro 开启授权信息缓存，关闭认证信息缓存，主要原因是授权数据量通常较大，缓存的性能提升更明显。</div>
          </div>
          <div class="card-source">来源: 1.9.1 Realm缓存</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">模式</div>
          <div class="card-question">Shiro 的权限字符串（Permission String）遵循什么规则？其中 `:`、`,` 和 `*` 各自代表什么含义？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">模式</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">权限字符串的规则是“资源标识符:操作”。<br>- `:`：用作资源、操作、实例之间的分隔符。例如 `user:query`。<br>- `,`：用于分隔同一部分中的多个值。例如 `user:query,edit` 表示同时拥有查询和编辑权限。<br>- `*`：作为通配符，表示任意资源、操作或实例。例如 `user:*` 表示拥有对 user 资源的所有操作权限。</div>
          </div>
          <div class="card-source">来源: 1.7.2 Permission</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">技术</div>
          <div class="card-question">在整合 Shiro 与 Spring 时，如果出现 Session 失效问题，可能的原因是什么？文档中提到的解决方案是什么？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">技术</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">可能的原因是 Shiro 的 DefaultWebSessionManager 中默认的 Cookie 名称是 `JSESSIONID`，这与 Servlet 容器（如 Tomcat）的默认名称冲突，导致容器重新分配值而使 Shiro 的会话丢失。<br>解决方案是：在 Spring 配置中自定义 `sessionManager`，并为其配置一个 `sessionIdCookie`，将 Cookie 的名称修改为与 `JSESSIONID` 不同的值，例如 `sid`。</div>
          </div>
          <div class="card-source">来源: 1.5.5 出现Session失效异常后解决方案</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">理论</div>
          <div class="card-question">Shiro 中的授权（Authorization）涉及哪四个关键对象？请简要说明。</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">理论</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">授权涉及的四个关键对象是：<br>1.  **主体 (Subject)**：访问应用的用户。<br>2.  **资源 (Resource)**：用户可以访问的 URL、数据或业务方法等。<br>3.  **权限 (Permission)**：表示用户是否拥有操作某个资源的权力，是授权的原子单位。<br>4.  **角色 (Role)**：权限的集合，通常将角色赋予用户以便于管理。</div>
          </div>
          <div class="card-source">来源: 1.7 授权</div>
        </div>
      </div>
    </div>

    <div class="card-container" onclick="this.classList.toggle('flipped');">
      <div class="card">
        <div class="card-face card-front">
          <div class="card-category">机制</div>
          <div class="card-question">当一个请求的 URL 同时匹配 `filterChainDefinitions` 中的多个模式时，Shiro 如何决定使用哪个拦截器链？</div>
          <div class="card-footer">点击卡片查看答案</div>
        </div>
        <div class="card-face card-back">
          <div class="card-category">机制</div>
          <div class="card-answer-wrapper">
            <div class="card-answer">Shiro 采用“第一次匹配优先”的方式。它会从上到下按声明顺序检查 URL 模式，一旦找到第一个匹配的模式，就会使用该模式对应的拦截器链，并停止后续的匹配。</div>
          </div>
          <div class="card-source">来源: 1.4.3 URL匹配顺序</div>
        </div>
      </div>
    </div>

      </div>
    </body>
    </html>
